// --------------------------------------------------------------------------------------------------------------------
// <copyright file="KinectIRObjectDetectorConfiguration.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Vision.ObjectDetection
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Windows;
    using System.Xml;
    using System.Xml.Serialization;

    using Microsoft.Robotics.Numerics;
    using Microsoft.Robotics.PhysicalObjects;

    /// <summary>
    /// Configuration parameters for a kinect IR object detector
    /// </summary>
    [DataContract]
    public class KinectIRObjectDetectorConfiguration
    {
        /// <summary>
        /// Level 1 classifier
        /// </summary>
        [DataMember]
        public readonly MemoryStream Level1Classifier;

        /// <summary>
        /// Level 2 classifier
        /// </summary>
        [DataMember]
        public readonly MemoryStream Level2Classifier;

        /// <summary>
        /// Level 2 Regression estimator
        /// </summary>
        [DataMember]
        public readonly MemoryStream Level2Regress;

        /// <summary>
        /// Level 2 Orientation regression estimator
        /// </summary>
        [DataMember]
        public readonly MemoryStream Level2OrientationRegress;

        /// <summary>
        /// Cluster confidence estimator
        /// </summary>
        [DataMember]
        public readonly MemoryStream ConfidenceEstimator;

        /// <summary>
        /// Level 2 patch size
        /// </summary>
        [DataMember]
        public readonly Size Level2Patch;

        /// <summary>
        /// Level 1 classifier threshold
        /// </summary>
        [DataMember]
        public readonly double Level1Threshold;

        /// <summary>
        /// Level 2 classifier threshold
        /// </summary>
        [DataMember]
        public readonly double Level2Threshold;

        /// <summary>
        /// Post processor confidence threshold
        /// </summary>
        [DataMember]
        public readonly double ConfidenceThreshold;

        /// <summary>
        /// Scale factor for object patch to include context
        /// </summary>
        [DataMember]
        public readonly double PatchContextScale;

        /// <summary>
        /// Factor for normalizing the depth regression window
        /// </summary>
        [DataMember]
        public readonly double DepthNormalizationFactor;

        /// <summary>
        /// Value to use for no reading
        /// </summary>
        [DataMember]
        public readonly short NoReadingDepthValue;

        /// <summary>
        /// Initializes a new instance of the <see cref="KinectIRObjectDetectorConfiguration" /> class
        /// </summary>
        /// <param name="manipulatedObjectDescription">Detector object description</param>
        /// <param name="level1Classifier">Level 1 classifier</param>
        /// <param name="level2Classifier">Level 2 classifier</param>
        /// <param name="level2Regress">Level 2 regression</param>
        /// <param name="level2OrientationRegress">Level 2 orientation regression</param>
        /// <param name="confidenceEstimator">Confidence post processor estimator</param>
        /// <param name="level2Patch">Level 2 patch size</param>
        /// <param name="level1Threshold">Level 1 classification threshold</param>
        /// <param name="level2Threshold">Level 2 classification threshold</param>
        /// <param name="confidenceThreshold">Post processor confidence threshold</param>
        /// <param name="patchScale">Patch scaling for including context</param>
        /// <param name="depthNormalizationFactor">Depth normalization factor - for scaling the depth regression</param>
        /// <param name="noReadingDepthValue">No measurement depth value</param>
        public KinectIRObjectDetectorConfiguration(
            PhysicalObjectDescription manipulatedObjectDescription,
            MemoryStream level1Classifier,
            MemoryStream level2Classifier,
            MemoryStream level2Regress,
            MemoryStream level2OrientationRegress,
            MemoryStream confidenceEstimator,
            Size level2Patch,
            double level1Threshold,
            double level2Threshold,
            double confidenceThreshold,
            double patchScale,
            double depthNormalizationFactor,
            short noReadingDepthValue)
        {
            this.ManipulatedObjectDescription = manipulatedObjectDescription;
            this.Level2Patch = level2Patch;
            this.PatchContextScale = patchScale;
            this.DepthNormalizationFactor = depthNormalizationFactor;
            this.Level1Threshold = level1Threshold;
            this.Level2Threshold = level2Threshold;
            this.ConfidenceThreshold = confidenceThreshold;
            this.NoReadingDepthValue = noReadingDepthValue;

            this.Level1Classifier = level1Classifier;
            this.Level2Classifier = level2Classifier;
            this.Level2Regress = level2Regress;
            this.Level2OrientationRegress = level2OrientationRegress;
            this.ConfidenceEstimator = confidenceEstimator;
        }

        /// <summary>
        /// Gets the physical object for the configuration
        /// </summary>
        [DataMember]
        public PhysicalObjectDescription ManipulatedObjectDescription { get; private set; }

        /// <summary>
        /// Gets the object width in mm
        /// </summary>
        public int ObjectWidthMM
        {
            get
            {
                return (int)(this.ManipulatedObjectDescription.WidthInM * MathConstants.MilliunitsPerUnit);
            }
        }

        /// <summary>
        /// Gets the object height in mm
        /// </summary>
        public int ObjectHeightMM
        {
            get
            {
                return (int)(this.ManipulatedObjectDescription.HeightInM * MathConstants.MilliunitsPerUnit);
            }
        }

        /// <summary>
        /// Gets the object depth in mm
        /// </summary>
        public int ObjectDepthMM
        {
            get
            {
                return (int)(this.ManipulatedObjectDescription.DepthInM * MathConstants.MilliunitsPerUnit);
            }
        }

        /// <summary>
        /// Create a configuration from file
        /// </summary>
        /// <param name="filename">File to load </param>
        /// <returns>The created configuration object</returns>
        /// <exception cref="FileNotFoundException">Thrown if input file does not exist</exception>
        public static KinectIRObjectDetectorConfiguration Create(string filename)
        {
            if (File.Exists(filename))
            {
                KinectIRObjectDetectorConfiguration detect = null;

                using (FileStream sr = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    DataContractSerializer ds = new DataContractSerializer(typeof(KinectIRObjectDetectorConfiguration), new List<Type>() { typeof(KinectIRObjectDetectorConfiguration) });
                    XmlDictionaryReaderQuotas quota = new XmlDictionaryReaderQuotas();

                    // 4 MMb should be enough to read net files
                    quota.MaxArrayLength = 2 << 21;
                    XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(sr, quota);
                    detect = (KinectIRObjectDetectorConfiguration)ds.ReadObject(reader, true);
                }

                return detect;
            }

            throw new FileNotFoundException(string.Format("Cannot create Kinect object detector because configuration file {0} does not exist", filename));
        }
    }
}
